#include <ctime>
#include <iostream>
#include <string>
#include <boost/bind/bind.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio.hpp>
#include <boost/array.hpp>

using boost::asio::ip::tcp;

std::string make_daytime_string()
{
    using namespace std; // For time_t, time and ctime;
    time_t now = time(0);
    return ctime(&now);
}

class tcp_connection : public boost::enable_shared_from_this<tcp_connection>
{
public:
    ~tcp_connection(){
        std::cout << "free tcp_connection" << std::endl;
    }
    typedef boost::shared_ptr<tcp_connection> pointer;

    static pointer create(boost::asio::io_context& io_context)
    {
        return pointer(new tcp_connection(io_context));
    }

    tcp::socket& socket()
    {
        return socket_;
    }

    void start()
    {
        start_read();
    }

    void start_read(){
        socket_.async_read_some(boost::asio::buffer(data),
                boost::bind(&tcp_connection::handle_read, shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
    }
    void start_write(){
        boost::asio::async_write(socket_, boost::asio::buffer(message_),
                boost::bind(&tcp_connection::handle_write, shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
    }
    void write(const char * buf, int size){
        //memcpy(buf -> send buffer); 
        message_.assign(buf, size);
        start_write();
    }

    void disconnect(){
        if(socket_.is_open()){
            //socket_.cancel();
            //socket_.shutdown();
            socket_.close();
        }
    }

private:
    tcp_connection(boost::asio::io_context& io_context)
        : socket_(io_context)
    {
    }

    void handle_write(const boost::system::error_code& error,
            size_t bytes_transferred)
    {
        std::cout << "write bytes:" << bytes_transferred << std::endl;
    }
    void handle_read(const boost::system::error_code & error, 
        size_t bytes_transferred)
    {
        std::cout << "read bytes:" << bytes_transferred << std::endl;
        if(!error){
            // decode && biz code callback 
            if(data[0] == '1'){
                write("HelloWorld", 10);
            }
            else{
            }
            start_read();
        }
        else{
            //error -> 9 : -> active close
            //error -> asio.misc:2 -> passive close
            std::cout << "handle read got error:[" << error << "]";
            std::cout << "socket open status:" << socket_.is_open()  << std::endl;
            disconnect();
            std::cout << "socket open status:" << socket_.is_open() << std::endl;
        }
    }

    tcp::socket socket_;
    std::string message_;
    boost::array<char, 1024> data;
};

class tcp_server
{
public:
    tcp_server(boost::asio::io_context& io_context)
        : io_context_(io_context),
        acceptor_(io_context, tcp::endpoint(tcp::v4(), 1234))
    {
        start_accept();
    }
private:
    void start_accept()
    {
        tcp_connection::pointer new_connection =  tcp_connection::create(io_context_);
        acceptor_.async_accept(new_connection->socket(),
                boost::bind(&tcp_server::handle_accept, this, new_connection,
                    boost::asio::placeholders::error));
    }

    void handle_accept(tcp_connection::pointer new_connection,
            const boost::system::error_code& error)
    {
        if (!error)
        {
            std::cout << "connected endpoint:" << new_connection->socket().remote_endpoint() << std::endl;
            new_connection->start();
        }

        start_accept();
    }

    boost::asio::io_context& io_context_;
    tcp::acceptor acceptor_;
};

int main()
{
    try
    {
        boost::asio::io_context io_context;
        tcp_server server(io_context);
        io_context.run();
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }

    return 0;
}
